namespace Yaaf.WP7.NativeAccess.Registry
{
    using System;
    using System.Collections.Generic;

    using Microsoft.Phone.Logging;

    using Yaaf.WP7.NativeAccess.Helper;
    using Yaaf.WP7.NativeAccess.Registry.Internals.AllDevices;
    using Yaaf.WP7.NativeAccess.Registry.Internals.HTC;
    using Yaaf.WP7.NativeAccess.Registry.Internals.LG;

    public interface IRegistryAccess
    {
        void SetValue(RootKey root, string subKey, string valueName, object value, RegistryValueKind valueKind);

        void DeleteKey(RootKey root, string subKey);

        void DeleteValue(RootKey root, string subKey, string valueName);
        
        RegistryValueKind GetValueType(RootKey root, string subKey, string valueName);

        object GetValue(RootKey root, string subKey, string valueName);

        IEnumerable<string> GetSubKeyNames(RootKey root, string subKey);

        IEnumerable<string> GetValuesNames(RootKey root, string subKey);
    }

    /// <summary>
    /// This class abstracts the registry
    /// a keyPath is a path to a key (as usual)
    /// a valueFullPath is the complete path to a value "keyPath\valueName" valueName can be empty
    /// </summary>
    public class Registry : AccessManager<IRegistryAccess>
    {
        public const string HkeyCurrentUserShort = "HKCU";

        public const string HkeyClassesRootShort = "HKCR";

        public const string HkeyLocalMachineShort = "HKLM";

        public const string HkeyCurrentUser = "HKEY_CURRENT_USER";

        public const string HkeyLocalMachine = "HKEY_LOCAL_MACHINE";

        public const string HkeyClassesRoot = "HKEY_CLASSES_ROOT";

        public const string HkeyUsers = "HKEY_USERS";

        private static bool isInitialized = false;
        public static new void Initialize()
        {
            if (isInitialized)
            {
                return;
            }
            isInitialized = true;
            Instance = new Registry();
            Instance.Initialize();

            if (IsAvailable)
            {
                Wp7.AdvancedExplorer.Logger.Instance.WriteLine("Using following RegistryAccess: {0}, Device: {1}", Access.GetType().FullName, DeviceSwitcher.DeviceType);
            }
            else
            {
                Wp7.AdvancedExplorer.Logger.Instance.WriteLine(
                    "Registry Access not available! (Device: {0})", DeviceSwitcher.DeviceType);
            }
        }

        public static void SetValue(string valueFullPath, object value, RegistryValueKind valueKind = RegistryValueKind.Unknown)
        {
            CheckAccess();
            string valueName;
            RootKey root;
            string subkey;
            GetValueInfo(valueFullPath, out root, out subkey, out valueName);
            var unifiedPath = ToShortRepresentation(root) + "\\" + subkey;
            if (value == null)
            {
                Access.DeleteValue(root, subkey, valueName);
            }
            else
            {
                Access.SetValue(root, subkey, valueName, value, valueKind);
            }
        }

        private static void CheckAccess()
        {
            if (!IsAvailable || Access == null)
            {
                throw new UnauthorizedAccessException("No Access on the Registry!");
            }
        }

        public static void DeleteKey(string keyFullPath)
        {
            CheckAccess();
            RootKey root;
            string subKey;
            GetKeyInfo(keyFullPath, out root, out subKey);
            Access.DeleteKey(root, subKey);
        }

        public static string ToShortRepresentation(RootKey root)
        {
            switch (root)
            {
                case RootKey.E_HKEY_CLASSES_ROOT:
                    return HkeyClassesRootShort;
                    break;
                case RootKey.E_HKEY_CURRENT_USER:
                    return HkeyCurrentUserShort;
                    break;
                case RootKey.E_HKEY_LOCAL_MACHINE:
                    return HkeyLocalMachineShort;
                    break;
                //case HTCRegistry.RootKey.E_HKEY_USERS:
                default:
                    throw new ArgumentOutOfRangeException("root");
            }
        }

        public static RegistryValueKind GetValueType(string valueFullPath)
        {
            CheckAccess();
            string valueName;
            RootKey root;
            string subkey;
            GetValueInfo(valueFullPath, out root, out subkey, out valueName);

            return Access.GetValueType(root, subkey, valueName);
        }

        public static object GetValue(string valueFullPath)
        {
            CheckAccess();
            string valueName;
            RootKey root;
            string subkey;
            GetValueInfo(valueFullPath, out root, out subkey, out valueName);
            return Access.GetValue(root, subkey, valueName);
        }

        internal static void GetKeyInfo(string keyFullPath, out RootKey root, out string subKey)
        {
            if (string.IsNullOrEmpty(keyFullPath))
            {
                throw new ArgumentException("Invalid path (" + keyFullPath + ")");
            }

            var first = keyFullPath.IndexOf("\\");
            if (first < 0)
            {
                root = MakeUnifiedRoot(keyFullPath);
                subKey = "";
                return;
            }

            root = MakeUnifiedRoot(keyFullPath.Substring(0, first));
            subKey = keyFullPath.Substring(first + 1);
        }

        internal static void GetValueInfo(string valueFullpath, out RootKey root, out string subkey, out string valueName)
        {
            if (string.IsNullOrEmpty(valueFullpath))
            {
                throw new ArgumentException("valueFullPath is empty!");
            }

            var first = valueFullpath.IndexOf("\\");
            var last = valueFullpath.LastIndexOf("\\");
            if (first < 0 || last < 0 || first == last)
            {
                throw new ArgumentException("Invalid fullpath (" + valueFullpath + ")");
            }

            root = MakeUnifiedRoot(valueFullpath.Substring(0, first));
            subkey = valueFullpath.Substring(first + 1,  last - first - 1);
            valueName = valueFullpath.Substring(last + 1);
        }

        private static RootKey MakeUnifiedRoot(string rootString)
        {
            switch (rootString)
            {
                case HkeyCurrentUser:
                case HkeyCurrentUserShort:
                    return RootKey.E_HKEY_CURRENT_USER;
                case HkeyLocalMachine:
                case HkeyLocalMachineShort:
                    return RootKey.E_HKEY_LOCAL_MACHINE;
                case HkeyClassesRoot:
                case HkeyClassesRootShort:
                    return RootKey.E_HKEY_CLASSES_ROOT;
                case HkeyUsers:
                    return RootKey.E_HKEY_USERS;
                default:
                    throw new ArgumentException("Invalid Root! (" + rootString + ")");
            }
        }


        private static readonly string[] rootKeys = new string[] { HkeyClassesRoot, HkeyCurrentUser, HkeyLocalMachine/*, "HK_USERS"*/ };

        public static IEnumerable<string> GetSubKeyNames(string keyFullPath)
        {
            CheckAccess();
            if (keyFullPath != null && keyFullPath.EndsWith("\\"))
            {
                keyFullPath = keyFullPath.Substring(0, keyFullPath.Length - 1);
            }

            // return root keys if needed
            if (string.IsNullOrEmpty(keyFullPath))
            {
                var list = new List<string>();
                for (int i = 0; i < rootKeys.Length; i++)
                {
                    var ptr = rootKeys[i];
                    list.Add(ptr);
                }
                return list;
            }
            
            RootKey root;
            string subKey;
            GetKeyInfo(keyFullPath, out root, out subKey);
            return Access.GetSubKeyNames(root, subKey); 
        }

        public static IEnumerable<string> GetValuesNames(string keyFullPath)
        {
            CheckAccess();
            if (keyFullPath != null && keyFullPath.EndsWith("\\"))
            {
                keyFullPath = keyFullPath.Substring(0, keyFullPath.Length - 1);
            }

            if (string.IsNullOrEmpty(keyFullPath))
            {
                return new List<string>();
            }

            RootKey root;
            string subKey;
            GetKeyInfo(keyFullPath, out root, out subKey);
            return Access.GetValuesNames(root, subKey);
        }

        protected override IRegistryAccess GetAccessFromDevice(DeviceManufacturer manufacturer)
        {
            IRegistryAccess access = null;
            switch (manufacturer)
            {
                case DeviceManufacturer.Samsung:
                case DeviceManufacturer.ASUS:
                case DeviceManufacturer.Dell:
                case DeviceManufacturer.Unknown:
                    try
                    {
                        access = new RegistryBase();
                    }
                    catch (RegisterComException)
                    {
                    }
                    break;
                case DeviceManufacturer.HTC:
                    access = new HtcRegistryAccess();
                    break;
                case DeviceManufacturer.LG:
                    access = new LgRegistryAccess();
                    break;
                default:
                    break;
            }
            return access;
        }
    }

    public enum RootKey
    {
        E_HKEY_CLASSES_ROOT,
        E_HKEY_CURRENT_USER,
        E_HKEY_LOCAL_MACHINE,
        E_HKEY_USERS
    }

    public enum RegistryValueKind
    {
        /// <summary>
        /// A 32-bit binary number. This value is equivalent to the Win32 API registry data type REG_DWORD.
        /// </summary>
        DWord,

        /// <summary>
        /// A null-terminated string. This value is equivalent to the Win32 API registry data type REG_SZ.
        /// </summary>
        String,

        /// <summary>
        /// Binary data in any form. This value is equivalent to the Win32 API registry data type REG_BINARY. 
        /// </summary>
        Binary,

        /// <summary>
        /// An array of null-terminated strings, terminated by two null characters. This value is equivalent to the Win32 API registry data type REG_MULTI_SZ.
        /// </summary>
        MultiString,

        ExpandString,
        Long,
        /// <summary>
        /// An unsupported registry data type. For example, the Microsoft Win32 API registry data type REG_RESOURCE_LIST is unsupported. Use this value to specify that the SetValue method should determine the appropriate registry data type when storing a name/value pair.
        /// </summary>
        Unknown
    }
}